package com.eugenes.concurrency;

import com.eugenes.concurrency.runnables.SinAdder;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class AddingSinus {

    private static final double EPS = 1e-9;

    private final int nThreads;
    private final int N;
    private final List<Thread> threads;
    private final List<SinAdder> tasks;

    public AddingSinus(int nThreads, int N) {
        this.nThreads = nThreads;
        this.N = N;
        this.threads = new ArrayList<>(nThreads);
        this.tasks = new ArrayList<>(nThreads);
    }

    private void awaitThreads(Collection<? extends Thread> threads) throws InterruptedException {
        for (Thread t : threads) {
            t.join();
        }
    }

    private void broadcastTasks(int taskSize) {
        for (int i=-N; i<=N; i+=taskSize) {
            SinAdder ss = new SinAdder(i, Math.min(i + taskSize - 1, N));
            Thread thread = new Thread(ss);

            threads.add(thread);
            tasks.add(ss);
        }
    }

    private void startThreads() {
        for (Thread t : threads) {
            t.start();
        }
    }

    private double getReducedResult() {
        double result = 0.0;
        for (SinAdder sa : tasks) {
            result += sa.getResult();
        }
        return result;
    }

    private void checkAssertResult(double result) {
        assert (result < EPS) : "Result is wrong: " + result;
    }

    private double perform() {
        long startTime = System.currentTimeMillis();

        int count = 2 * N + 1;
        int taskSize = count / nThreads + (count % nThreads == 0 ? 0 : 1);

        broadcastTasks(taskSize);
        startThreads();

        try {
            awaitThreads(threads);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }

        double result = getReducedResult();
        checkAssertResult(result);

        System.err.println("\nResult: " + result + "\n" +
                "Time: " + (System.currentTimeMillis() - startTime) + "ms.\n");
        
        return result;
    }

    public static void main(String[] args) {
        int nThreads;
        int N;

        try {
            nThreads = Integer.parseInt(args[0]);
            N = Integer.parseInt(args[1]);
            if (nThreads <= 0 || nThreads > 64 || N < 0) {
                throw new IllegalArgumentException();
            }
        } catch (Exception ex) {
            System.out.println("Illegal arguments. Try again.\n" +
                    "Arguments: number of threads (>0, <=64) and N (>0).\n");
            return;
        }

        new AddingSinus(nThreads, N).perform();
    }
}
